package com.futurescale.sa.model.vo
{
	import com.futurescale.sa.model.enum.StoryTypeEnum;
	
	import mx.utils.StringUtil;

	/**
	 * A Story could be a standalone work or an Episode of a 
	 * Series. Its Chapters may optionally be grouped by
	 * Parts, and it has a Cast, a Milieu and one or 
	 * more Plots. This value object represents a single 
	 * Story. 
	 */
	[Bindable] 
	public class StoryVO extends ValueObject
	{
		/**
		 * The XML element name.
		 */
		public static const ELEMENT:String = "Story";
		
		/**
		 * Construct a StoryVO.
		 */
		public function StoryVO( xml:XML=null )
		{
			super( ELEMENT, xml );
		}
		
		/**
		 * The Cast stub.
		 */ 
		public function get cast():CastVO
		{
			return ( x.Cast[0] )?new CastVO( x.Cast[0] ):null;
		}
		public function set cast( c:CastVO ):void
		{
			x.Cast = c.xml;
		}
		
		/**
		 * The Milieu stub.
		 */ 
		public function get milieu():MilieuVO
		{
			return ( x.Milieu[0] )?new MilieuVO( x.Milieu[0] ):null;
		}
		public function set milieu( m:MilieuVO ):void
		{
			x.Milieu = m.xml;
		}
		
		/**
		 * The Series stub, if this is an Episode.
		 */ 
		public function get series():SeriesVO
		{
			return ( x.Series[0] )?new SeriesVO( x.Series[0] ):null;
		}
		public function set series( s:SeriesVO ):void
		{
			x.Series = s.xml;
		}
		
		/**
		 * Is this an Episode of a Series?
		 */ 
		public function get isEpisode():Boolean
		{
			return ( x.Series[0] );
		}
		public function set isEpisode( e:Boolean ):void
		{} // read only but bindable
		
		/**
		 * What type of Story is this?
		 * StoryTypeEnum.SIMPLE  = Scenes only
		 * StoryTypeEnum.NORMAL  = Chapters, and Scenes
		 * StoryTypeEnum.COMPLEX = Parts, Chapters, and Scenes
		 */ 
		public function get type():StoryTypeEnum
		{
			return (x.@type!=undefined)?StoryTypeEnum.list[ Number( x.@type ) ]:null; 
		}
		public function set type( t:StoryTypeEnum ):void
		{
			x.@type = t.ordinal;
		}
		
		/**
		 * Use Scenes only for this Story?
		 */ 
		public function get useScenes():Boolean
		{
			return ( this.type == StoryTypeEnum.SIMPLE );
		}
		public function set useScenes(u:Boolean):void
		{
			this.type = StoryTypeEnum.SIMPLE;
		}
		
		/**
		 * Use Chapters for this Story?
		 */ 
		public function get useChapters():Boolean
		{
			return ( this.type == StoryTypeEnum.NORMAL );
		}
		public function set useChapters(u:Boolean):void
		{
			this.type = StoryTypeEnum.NORMAL;
		}
		
		/**
		 * Use Parts for this Story?
		 */ 
		public function get useParts():Boolean
		{
			return ( this.type == StoryTypeEnum.COMPLEX );
		}
		public function set useParts(u:Boolean):void
		{
			this.type = StoryTypeEnum.COMPLEX;
		}
		
		/** 
		 * An ordinal-sorted Vector of a SIMPLE Story's Scenes.
		 */
		public function get scenes():Vector.<SceneVO>
		{
			var v:Vector.<SceneVO> = new Vector.<SceneVO>();
			var xl:XMLList = xml..Scene;
			for each ( var sx:XML in xl ) {
				var scene:SceneVO = new SceneVO(sx);
				v.push(scene);
			}
			v.sort(ValueObject.ordinalSort);
			return v;
		}
		public function set scenes( v:Vector.<SceneVO> ):void 
		{} // read-only but bindable
		
		/** 
		 * An ordinal-sorted Vector of a NORMAL Story's Chapters.
		 */
		public function get chapters():Vector.<ChapterVO>
		{
			var v:Vector.<ChapterVO> = new Vector.<ChapterVO>();
			var xl:XMLList = xml..Chapter;
			for each ( var cx:XML in xl ) {
				var chapter:ChapterVO = new ChapterVO(cx);
				v.push(chapter);
			}
			v.sort(ValueObject.ordinalSort);
			return v;
		}
		public function set chapters( v:Vector.<ChapterVO> ):void 
		{} // read-only but bindable
		
		/** 
		 * An ordinal-sorted Vector of a COMPLEX Story's Parts.
		 */
		public function get parts():Vector.<PartVO>
		{
			var v:Vector.<PartVO> = new Vector.<PartVO>();
			if (useParts) {
				var xl:XMLList = xml.child(PartVO.ELEMENT);
				for each ( var px:XML in xl ) {
					var part:PartVO = new PartVO(px);
					v.push(part);
				}
				v.sort(ValueObject.ordinalSort);
			}
			return v;
		}
		public function set parts( v:Vector.<PartVO> ):void 
		{} // read-only but bindable
		
		/**
		 * Get a new Scene for the Story.
		 */
		public function getNewScene():SceneVO
		{
			var scene:SceneVO = new SceneVO();
			addScene( scene );
			return scene;
		}
		
		/**
         * Get a new Chapter for the Story. 
		 */
		public function getNewChapter(  ):ChapterVO
		{
			var chapter:ChapterVO = new ChapterVO();
			addChapter( chapter );
			return chapter;
		}
		
		/**
		 * Get a new Part for the Story.
		 */
		public function getNewPart():PartVO
		{
			var part:PartVO = new PartVO();
			addPart( part );
			return part;
		}
		
		
		/**
		 * Add a Scene to the Story, renumbering the existing
		 * Scenes if the added Scene already has an ordinal less
		 * than the next Scene ordinal. Otherwise next Scene 
		 * ordinal is assigned.
		 * 
		 * Forces the story type to NORMAL if no part argument
		 */
		public function addScene( scene:SceneVO ):void
		{
			if ( scene.ordinal > 0 && scene.ordinal < nextPartOrdinal ) {
				var scenes:Vector.<SceneVO> = this.scenes;
				for each (var svo:SceneVO in scenes ){
					if (svo.ordinal >= scene.ordinal) svo.ordinal++;
				}
			} else {
				scene.ordinal = nextSceneOrdinal;
			}
			xml.appendChild( scene.xml );
			useScenes=true;
		}
		
		/** 
		 * Delete a Scene from the Story, renumbering
		 */
		public function deleteScene( scene:SceneVO  ):void
		{
			var sceneOrd:Number = scene.ordinal;
			delete xml..Scene.(@uid == scene.uid)[0];
			var scenes:Vector.<SceneVO> = this.scenes;
			for each (var svo:SceneVO in scenes ){
				if (svo.ordinal > scene.ordinal) svo.ordinal--;
			}
		}
		
		/**
		 * Add a Chapter to the Story.
		 * Renumbers the existing Chapters if the added Chapter 
		 * already has an ordinal less than the next Chapter 
		 * ordinal. Otherwise next Chapter ordinal is assigned.
		 * 
		 * Forces the story type to NORMAL if no part argument
		 */
		public function addChapter( chapter:ChapterVO ):void
		{
			// renumber chapters if inserting
			if ( chapter.ordinal > 0 && chapter.ordinal < nextChapterOrdinal ) {
				var chapters:Vector.<ChapterVO> = this.chapters;
				for each ( var cvo:ChapterVO in chapters ){
					if ( cvo.ordinal >= chapter.ordinal ) cvo.ordinal++;
				}
			} else {
				chapter.ordinal = nextChapterOrdinal;
			}
			xml.appendChild( chapter.xml );
			useChapters=true;
		}
		
		/** 
		 * Delete a Chapter from the Story, renumbering
		 */
		public function deleteChapter( chapter:ChapterVO  ):void
		{
			var chapterOrd:Number = chapter.ordinal;
			delete xml..Chapter.(@uid == chapter.uid)[0];
			var chapters:Vector.<ChapterVO> = this.chapters;
			for each ( var cvo:ChapterVO in chapters ){
				if ( cvo.ordinal > chapterOrd ) cvo.ordinal--;
			}
		}
		
		/**
		 * Add a Part to the Story, renumbering the existing
		 * Parts if the added Part already has an ordinal less
		 * than the next Part ordinal. Otherwise Part is assigned
		 * next Part ordinal.
		 * 
		 * Forces the story type to COMPLEX
		 */
		public function addPart( part:PartVO ):void
		{
			if ( part.ordinal > 0 && part.ordinal < nextPartOrdinal ) {
				var parts:Vector.<PartVO> = this.parts;
				for each (var pvo:PartVO in parts ){
					if (pvo.ordinal >= part.ordinal) pvo.ordinal++;
				}
			} else {
				part.ordinal = nextPartOrdinal
			}
			xml.appendChild( part.xml );
			useParts=true;
		}
		
		/** 
		 * Delete a Part from the Story, renumbering
		 */
		public function deletePart( part:PartVO ):void
		{
			var partOrd:Number = part.ordinal;
			delete xml..Part.(@uid == part.uid)[0];
			var parts:Vector.<PartVO> = this.parts;
			for each (var pvo:PartVO in parts ){
				if (pvo.ordinal > partOrd) pvo.ordinal--;
			}
		}
		
		/**
		 * Get the text of the current Draft of each Scene
		 * in the Story. Story title is optionally included 
		 * at the beginning of the text.
		 *  
		 * If using Parts and there is more than one, part 
		 * headings are included.
		 *  
		 * If using Chapters and there is more than one, Chapter 
		 * headings are included.
		 */
		public function getText( includeHeading:Boolean=false ):String
		{
			var text:String = includeHeading ? name + DOUBLE_SPACE : "";
			if (useParts) {
				var partList:Vector.<PartVO> = parts;
				if (partList.length > 1) {
					// multiple parts with headings
					for each ( var pvo:PartVO in partList) {
						text += pvo.getText(true) + DOUBLE_SPACE;
					}
				} else if (partList.length == 1){
					// single part sans heading
					text += PartVO(partList[0]).getText(false);
				}
			} else if (useChapters) {
				var chapterList:Vector.<ChapterVO> = chapters;
				if (chapterList.length > 1) {
					// multiple chapters with headings
					for each ( var cvo:ChapterVO in chapterList) {
						text += cvo.getText(true) + DOUBLE_SPACE;
					}
				} else if (chapterList.length == 1){
					// single chapter sans heading
					text += ChapterVO(chapterList[0]).getText(false);
				}
			} else if (useScenes) {
				var sceneList:Vector.<SceneVO> = scenes;
				if (sceneList.length > 1) {
					// multiple scene with headings
					for each ( var svo:SceneVO in sceneList) {
						text += svo.getText(true) + DOUBLE_SPACE;
					}
				} else if (sceneList.length == 1){
					// single scene sans heading
					text += SceneVO(sceneList[0]).getText(false);
				}
			}
			
			// read-only text of story
			return text;
		}
		
		/**
		 * The word count for the full text of the Story.
		 */
		public function get wordCount():Number
		{
			var wc:Number = 0;
			var storyText:String = StringUtil.trim(getText(false));
			if ( storyText.length > 0 ) {
				wc = storyText.split(/\s+/g).length;
			}
			return wc;
		}
		
		public function set wordCount( c:Number ):void
		{} // read only but bindable

		/**
         * Get the next Part ordinal for this Story.
         * For COMPLEX stories with Parts, Chapters, and Scenes.
		 */
		private function get nextPartOrdinal():Number
		{
			return getNextOrdinal( xml..Part );
		}

		/**
		 * Get the next Chapter ordinal for this Story.
		 * For NORMAL stories with Scenes and Chapters.
		 */
		private function get nextChapterOrdinal():Number
		{
			return getNextOrdinal( xml..Chapter );
		}
	
		/**
		 * Get the next Scene ordinal for this Story.
		 * For SIMPLE stories with Scenes only.
		 */
		private function get nextSceneOrdinal():Number
		{
			return getNextOrdinal( xml..Scene );
		}
	}
}